#include "degfx/driver.h"

#ifdef MX__DRIVER_DJGPP

#   define MXMODULE_DRS

#   include <pc.h>
#   include <dpmi.h>
#   include <go32.h>
#   include <conio.h>
#   include <assert.h>
#   include <sys/farptr.h>

extern unsigned mx__djgpp_mouse_valid;
extern unsigned mx__djgpp_pointer(void);
extern unsigned mx__djgpp_key(int *scan, int *ascii);

static unsigned mx__djgpp_vga_start(MX_GFX_ARGS * args)
{
	__dpmi_regs r;

	textmode(C80);

	/* Set graphics mode */
	r.x.ax = 0x12;
	__dpmi_int(0x10, &r);

	/* Reset the mouse driver */
	r.x.ax = 0x0000;
	__dpmi_int(0x33, &r);
	if (r.x.ax)
		mx__djgpp_mouse_valid = true;
	else
		mx__djgpp_mouse_valid = false;

	/* Inform the user of the results */
	args->w = 640;
	args->h = 480;
	args->c = 4;
	args->title = "djgpp_vga";
	args->pointer = false;

	mx_drs_area(args->w - 1, args->h - 1);

	return true;
}

static void mx__djgpp_vga_stop(void)
{
	__dpmi_regs reg;

	reg.x.ax = 0x0003;
	__dpmi_int(0x10, &reg);

	textmode(C80);
}

/* To make flush function easier, x-coord must allready be rounded to 8 pixels */
static void mx__djgpp_vga_bitplane(unsigned long ptr, unsigned long endbyte, const MX_PIXEL * p, unsigned int colormask)
{
	unsigned int bitmask = 0x80;
	unsigned int writeword = 0x00;

	/* Copy whole bytes */
	while (ptr <= endbyte) {

		/* Check if a byte is ready to write */
		if (bitmask == 0) {
			_farnspokeb(0xa0000 + ptr, writeword);
			bitmask = 0x80;
			writeword = 0x00;
			++ptr;
		}

		/* Build the bitmask for this bitplane */
		if ((*p) & colormask)
			writeword |= bitmask;
		++p;
		bitmask >>= 1;
	}
}

static void mx__djgpp_vga__flush(const MX_RECT * rect, MX_PIXEL * array, const int stride)
{
	int y = rect->y1;

	/* Set the selector for fast acces to the screen */
	_farsetsel(_dos_ds);

	/* Push the buffer to the visible screen */
	while (y <= rect->y2) {
		const unsigned long width = rect->x2 - rect->x1;
		const unsigned long start = ((y * 640) + rect->x1) / 8;
		const unsigned long end = start + (width / 8);

		/* Convert the line to 4 bit color */
		MX_PIXEL *ptr = array;
		const MX_PIXEL *last = array + width + 1;

		while (ptr != last) {
			MX_PIXEL newpixel;
			const unsigned r = MXR(*ptr);
			const unsigned g = MXG(*ptr);
			const unsigned b = MXB(*ptr);

			if (r + b + g > 384)
				newpixel = 8;
			else
				newpixel = 0;
			if (-r + b + g > 127)
				newpixel |= 4;
			if (r + b - g > 127)
				newpixel |= 2;
			if (r - b + g > 127)
				newpixel |= 1;

			*ptr++ = newpixel;
		}

		/* X points must be rounded, bitplane writing need this */
		assert((rect->x1 % 8) == 0);
		assert((rect->x2 % 8) == 7);

		/* Copy the line to the VGA bitplanes */

		/* Select Blue bitplane */
		outportw(0x3c4, 0x0102);
		mx__djgpp_vga_bitplane(start, end, array, 0x01);

		/* Select Green bitplane */
		outportw(0x3c4, 0x0202);
		mx__djgpp_vga_bitplane(start, end, array, 0x02);

		/* Select Red bitplane */
		outportw(0x3c4, 0x0402);
		mx__djgpp_vga_bitplane(start, end, array, 0x04);

		/* Select Intensity bitplane */
		outportw(0x3c4, 0x0802);
		mx__djgpp_vga_bitplane(start, end, array, 0x08);

		/* Next row to flush */
		array += stride;
		++y;
	}
}

static void mx__djgpp_vga_flush(MX_RECT * rect)
{
	const int w = mx_w(mx__args.buffer);

	rect->x1 -= (rect->x1 % 8);
	rect->x2 += 7 - (rect->x2 % 8);

	/* Make sure the area is smaller than the buffer */
	if ((rect->x2 - rect->x1) > w) {
		rect->x2 = rect->x1 + w;
		rect->x2 -= ((rect->x2 + 1) % 8);
	}

	mx__gfx_flush(mx__djgpp_vga__flush, rect);
}

static unsigned mx__djgpp_vga_poll(void)
{
	mx_drs_update(mx__djgpp_vga_flush);
	return true;
}

static void mx__djgpp_vga_dirty(const MX_RECT * rect)
{
	mx_drs_dirty(rect, true);
}

const MX_DRIVER mx__djgpp_vga = {
	mx__djgpp_vga_start,
	mx__djgpp_vga_stop,

	mx__djgpp_vga_poll,
	mx__djgpp_vga_dirty,

	mx__djgpp_pointer,
	mx__djgpp_key
};

#endif
